/**
 * @file Define the Wall Service class
 * 
 * @author Steve Gao<gaofu@parllay.com>
 */
const mongoose = require('mongoose');
const async = require('async');
const sendcloud = require('sendcloud');
const path = require('path');
const fs = require('fs');

const models = require('../models/mongodb');
const utils = require('../utils');
const redisKey = require('../redisKey');
const wechat = require('../apis/wechat-api');

const { SystemServices, Configuration, WeChatCSMessageTypes, EmailTemplates } = require('../constants');

const { WeChatError } = require('../errors');

const WeChatAPI = require('../apis/wechat-api').WeChatAPI;
const BaseService = require('./base-service').BaseService;

/**
 * This class is used for functions about Wall
 * 
 * @class WallService
 */
class WallService extends BaseService {

    constructor(context) {
        super(context);
        this.initializeMongoContext();
        this.initializeModels();
    }

    get wallPhotoOperationMap() {
        return {
            approve: 'approved',
            reject: 'rejected',
            delete: 'deleted'
        };
    }

    get userService() {
        if (!this._userService) {
            this._userService = this.getSystemService(SystemServices.USER_SERVICE);
        }

        return this._userService;
    }

    get wechatService() {
        if (!this._wechatService) {
            this._wechatService = this.getSystemService(SystemServices.WECHAT_SERVICE);
        }

        return this._wechatService;
    }

    get mailService() {
        if (!this._mailService) {
            this._mailService = this.getSystemService(SystemServices.MAIL_SERVICE);
        }

        return this._mailService;
    }

    get wechatMessageService() {
        if (!this._wechatMessageService) {
            this._wechatMessageService = this.getSystemService(SystemServices.WECHAT_MESSAGE_SERVICE);
        }

        return this._wechatMessageService;
    }

    get crawlerService() {
        if (!this._crawlerService) {
            this._crawlerService = this.getSystemService(SystemServices.CRAWLER_SERVICE);
        }

        return this._crawlerService;
    }

    get wechatConfig() {
        return this.getApplicationConfiguration(Configuration.WECHAT);
    }

    get sendcloudConfig() {
        return this.getApplicationConfiguration(Configuration.SEND_CLOUD);
    }

    get applicationConfig() {
        return this.getApplicationConfiguration(Configuration.APPLICATION);
    }

    get mongoose() {
        return mongoose;
    }

    /**
     * Initialize MongoDB Context
     * 
     * @name initializeMongoContext
     */
    initializeMongoContext() {
        var mongooseConfig = this.getApplicationConfiguration(Configuration.MONGOOSE);

        if (mongooseConfig) {
            if (!mongooseConfig.options) {
                mongooseConfig.options = {
                    db: {
                        native_parser: true
                    },
                    server: {
                        poolSize: 100
                    }
                };
            }
            var connString = mongooseConfig.connection_string;
            if (mongooseConfig.user && mongooseConfig.pass) {
                options.user = mongooseConfig.user;
                options.pass = mongooseConfig.pass;
            }
            this.mongoose.connect(connString, mongooseConfig.options);
        }
    }

    get User() {
        return this._User;
    }
    get Business() {
        return this._Business;
    }
    get Wall() {
        return this._Wall;
    }
    get WallRealTimeConfig() {
        return this._WallRealTimeConfig;
    }
    get WallUser() {
        return this._WallUser;
    }
    get WallPhoto() {
        return this._WallPhoto;
    }
    get LatestUploadWallPhotos() {
        return this._LatestUploadWallPhotos;
    }
    get Comment() {
        return this._Comment;
    }
    get DemoRequest() {
        return this._DemoRequest;
    }
    get Sales() {
        return this._Sales;
    }
    get ManagedOfficialAccount() {
        return this._ManagedOfficialAccount;
    }
    get Tag() {
        return this._Tag;
    }
    get AssetTag() {
        return this._AssetTag;
    }
    /**
     * Initialize the mongoose models
     * 
     * @name initializeModels
     */
    initializeModels() {
        this._User = this.mongoose.model('user', models.User);
        this._Business = this.mongoose.model('business', models.Business);
        this._Wall = this.mongoose.model('wall', models.Wall);
        this._WallRealTimeConfig = this.mongoose.model('wall_realtime_config', models.WallRealTimeConfig);
        this._WallUser = this.mongoose.model('wall_user', models.WallUser);
        this._WallPhoto = this.mongoose.model('wall_photo', models.WallPhoto);
        this._LatestUploadWallPhotos = this.mongoose.model('latest_upload_wall_photos', models.LatestUploadWallPhotos);
        this._Comment = this.mongoose.model('wall_assets_comment', models.Comment);
        this._ManagedOfficialAccount = this.mongoose.model('managed_official_account', models.ManagedOfficialAccount);
        this._Sales = this.mongoose.model('sale', models.Sales);
        this._DemoRequest = this.mongoose.model('demo_request', models.DemoRequest);
        this._Tag = this.mongoose.model('tag', models.Tag);
        this._AssetTag = this.mongoose.model('asset_tag', models.AssetTag);
    }

    /**
     * Get the system user information by id
     * 
     * @name getUserProfile
     * @param {String} userId - The user id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getUserProfile(userId, parentCallback) {
        var me = this;
        me.User.findUserById(userId, function (error, user) {
            parentCallback(user);
        })
    }

    /**
     * Update the system user's information
     * 
     * @name updateUserProfile
     * @param {Object} data - The data need to update
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    //TODO: need to rewrite the fucking shit code
    updateUserProfile(data, parentCallback) {
        var me = this;
        !data.email && (delete data.email);
        !data.profile_image && (delete data.profile_image);
        !data.name && (delete data.name);
        !data.new_password && (delete data.new_password);
        !data.old_password && (delete data.old_password);
        async.waterfall([
            (callback) => { //Check if password need to change
                if (data.new_password && data.old_password) {
                    me.User.findUserById(data.id, (error, user) => {
                        if (error) {
                            parentCallback({
                                success: false,
                                error: '系统繁忙，请稍后再试！'
                            })
                        }
                        if (user.password === data.old_password) {
                            data.password = data.new_password;
                            callback(null);
                        } else {
                            parentCallback({
                                success: false,
                                error: '原密码输入错误'
                            });
                        }
                    });
                } else {
                    callback(null);
                }
            }, () => {
                me.User.findUserById(data.id, function (error, user) {
                    if (error) {
                        //TODO: Added log here
                        parentCallback({
                            success: false,
                        });
                    } else {
                        if (data.email) {
                            data.email = data.email.toLowerCase();
                        }
                        delete data.id;
                        user.set(data);
                        user.save(function (error, user) {
                            parentCallback({
                                success: true,
                                data: "OK"
                            });
                        })
                    }
                });
            }
        ]);
    }

    /**
     * Save web asset 
     * 
     * @name savePhotoAssset
     * @param {String} wallId - The wall id
     * @param {Object} 
     */
    savePhotoAssset(wallId, photoData, userInfo, parentCallback) {
        var me = this;
        photoData.photo_url = photoData.photo_url || photoData.url;
        var data = {
            wall_id: wallId,
            creator_id: userInfo.id,
            photo_url: this.getCDNImageURL(photoData.photo_url),
            description: photoData.description || photoData.message,
            asset_type: photoData.asset_type || photoData.assetType,
            ratio: photoData.ratio || 1,
            asset_meta_data: (photoData.asset_meta_data || {
                ratio: photoData.ratio,
                poster_image: (photoData.assetType === 'video' ? this.getCDNImageURL(photoData.photo_url + '?vframe/jpg/offset/1') : undefined),
                width: photoData.width || undefined,
                height: photoData.height || undefined
            }),
            operations: [{
                operator_id: userInfo.id,
                operation: 'uploaded',
                timestamp: Date.now()
            }],
            status: photoData.status || 'approved',
            source: photoData.source || 'PC'
        };

        if (photoData.additional_url) {
            data.additional_url = photoData.additional_url;
        }

        if (photoData.created_date) {
            data.created_date = photoData.created_date;
            data.last_updated_date = photoData.created_date;
            data.created_timestamp = new Date(photoData.created_date).getTime();
        } else {
            data.created_timestamp = Date.now();
        }

        var wallPhoto = new me.WallPhoto(data);
        data.user_id = userInfo.id;
        var latestWallPhotos = new me.LatestUploadWallPhotos(data);

        var returnVal = {};
        async.parallel({
            latestUpdate: (callback) => {
                /**
                 * 将数据备份进最新的所有上传的数据库
                 * 
                 */
                latestWallPhotos.save(function (error, photo) {
                    if (error) {
                        me.logger.error('Error happened when upload image: ', error);
                        callback(null, false);
                    } else {
                        callback(null, true);
                    }
                });
            },
            wallPhoto: (callback) => {
                wallPhoto.save(function (error, photo) {
                    if (error) {
                        me.logger.error('Error happened when upload image: ', error);
                        returnVal = {
                            success: false,
                            error: '系统繁忙，请稍后再试!'
                        };
                        callback(null, false);
                    } else {
                        returnVal = {
                            success: true,
                            data: {
                                id: photo.id,
                                photo_url: data.photo_url,
                                asset_meta_data: photo.asset_meta_data,
                                type: photo.asset_type,
                                source: photo.source,
                                likes: 0,
                                comments_count: 0,
                                description: photo.description,
                                status: photo.status,
                                timestamp: photo.last_updated_date.getTime(),
                                additional_url: photo.additional_url,
                                ratio: photo.ratio,
                                user: {
                                    id: userInfo.id,
                                    name: userInfo.name,
                                    profile_image: me.getCDNImageURL(userInfo.profile_image)
                                }
                            }
                        };
                        callback(null, true);
                    }
                });

            }
        }, (error, result) => {
            if (result.latestUpdate && result.wallPhoto) {
                parentCallback(returnVal);
            }
        });


    }

    /**
     * Create trial user
     * 
     * @name createTrialUser
     * @param {String} host - The server host name
     * @param {Object} data - The data for creating trial user
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    //TODO: Need more work today, need rewrite Anxu's code
    createTrialUser(host, data, parentCallback) {
        var me = this;
        var functionChain = [];
        var industryTemplate = null;
        var userName = utils.getEmailUserName(data.email);
        var savePoints = {};
        async.waterfall([
            (callback) => { // Get industry template
                let industryTemplateFile = path.resolve(
                    this.Context.getCurrentWorkDirectory(),
                    'supports/data/industries/template.json');
                if (fs.existsSync(industryTemplateFile)) {
                    var templateContent = fs.readFileSync(industryTemplateFile);
                    var templatesObject = JSON.parse(templateContent);
                    industryTemplate = templatesObject[data.industry];
                }
                if (!industryTemplate) {
                    parentCallback({
                        success: false,
                        error: "所选的行业暂不支持！"
                    })
                } else {
                    callback(null);
                }

            }, (callback) => { // Check if the email already requested trial
                me.User.findUserByEmail(data.email.toLowerCase(), (error, users) => {
                    if (error) {
                        //TODO: Add log here
                        parentCallback({
                            success: false,
                            error: '服务临时不可用，请稍后再试！'
                        });
                    } else if (users && users.length > 0) {
                        parentCallback({
                            success: false,
                            error: '此邮箱已经申请过试用，无法再次申请！'
                        });
                    } else {
                        callback(null);
                    }
                });
            }, (callback) => { // Create trial user
                var password = utils.generateIdentifier(8, 'full');
                var identifier = utils.generateIdentifier(); //生成8位随机密码
                var ref = (data.refs && data.refs.length > 0 && data.refs[data.refs.length - 1]) || 'direct';
                data.password = password;
                data.identifier = identifier;
                var user = new me.User({
                    name: userName,
                    email: data.email.toLowerCase(),
                    type: 'trial',
                    industry: data.industry,
                    password: password,
                    hash_id: identifier,
                    ref: ref
                });
                user.save(function (error, user) {
                    if (error) {
                        return parentCallback({
                            success: false,
                            error: '服务临时不可用，请稍后再试！'
                        });
                    }
                    data.user_id = user.id;
                    var userId = user.id;
                    callback(null, userId);
                });
                let date = user.created_date.toString();
                me.mailService.addMemberToMailchimpList(null, data.email.toLowerCase(), 'subscribed', {
                    "FNAME": userName,
                    "MMERGE3": `${ref}:trial`,
                    "MMERGE4": date
                }, (error, result) => {
                    //me.logger.info(result);
                });
            }, (userId, callback) => { //Generate trial plan for user
                var trialPlanId = 1003; //TODO: Get configuration from system configuration
                var trialDays = 14; //TODO: Get configuration from system configuration
                var orderId = 1000; //TODO: Get configuration from system configuration
                var trialType = 'daily'; //TODO: Get configuration from system configuration
                var createdBy = 2001; //Self service
                savePoints.userId = userId;
                me.userService.generateUserPlan(orderId, userId, trialPlanId, trialDays, 'daily', true, 2001, (data) => {
                    if (data.success) {
                        callback(null, userId);
                    } else {
                        me.rollbackTrialUser(data);
                        parentCallback({
                            success: false,
                            error: '服务器临时不可用，请稍后再试！'
                        });
                    }
                });
            }, (userId, callback) => { // Created trial wall for user
                var managedAppId = me.wechatConfig.managed_app_id;
                var wallData = {
                    name: userName,
                    app_id: managedAppId,
                    user_id: userId,
                    description: industryTemplate.description || '跨屏社区试用版'
                };
                var wall = me.Wall(wallData);
                wall.save(function (error, wall) {
                    if (error) {
                        parentCallback({
                            success: false,
                            error: '服务器临时不可用，请稍后再试！'
                        });
                    } else {
                        data.wall_id = wall.id;
                        callback(null, userId, wall.id);
                    }
                });
            }, (userId, wallId, callback) => { // Create photos for wall
                var functionChain = [];

                var photos = industryTemplate.photos || [];

                photos.forEach((photo) => {
                    functionChain.push((callback) => {
                        photo.source = "PC";
                        photo.status = "approved";
                        photo.wall_id = wallId;
                        photo.creator_id = userId;
                        photo.asset_meta_data = {
                            ratio: photo.ratio || 1
                        };
                        var photoObject = new me.WallPhoto(photo);
                        photoObject.save(function (error, photo) {
                            callback(null);
                        });
                    });
                });

                functionChain.push(() => {
                    callback(null, wallId);
                });

                async.waterfall(functionChain);
            }, (wallId, callback) => { // Create qrCode

                me.wechatService.createQRCode(me.wechatConfig.managed_app_id, {
                    is_persist: true,
                    value: `wall:${wallId}`
                }, (data) => {
                    if (data) {
                        var qrcodeURL = me.wechatService.getQRCodeURL(data.ticket);
                        callback(null, qrcodeURL, wallId);
                    } else {
                        parentCallback({
                            success: false,
                            error: '生成二维码时发生错误，请稍后再试！'
                        })
                    }
                });
            }, (qrcodeURL, wallId, callback) => {
                me.Wall.findWallById(wallId, function (error, wall) {
                    if (error) {
                        parentCallback({
                            success: false,
                            error: "服务器临时不可用，请稍后再试！"
                        });
                    } else {
                        if (industryTemplate.logo) {
                            wall.logo = industryTemplate.logo;
                        }
                        wall.qr_code = qrcodeURL;
                        wall.save(function (error, wall) {
                            callback(null, qrcodeURL, wallId);
                        });
                    }
                });
            }, (qrcodeURL, wallId, callback) => {
                var wallURL = `http://${host}/wall/${wallId}`;

                var values = {
                    WALL_URL: wallURL,
                    QRCODE_URL: qrcodeURL,
                    BILLBOARD_LOGIN_URL: `http://${host}/login`,
                    USER_PASSWORD: data.password
                };

                me.mailService.sendMailByTemplate(EmailTemplates.TRIAL_WELCOME, {
                    to: data.email,
                    cc: me.sendcloudConfig.cc, //TODO: should remove sendcloud configuration
                    subject: "欢迎您使用跨屏朋友圈！"
                }, values, (error, info) => {
                    if (error) {
                        callback(null, null, null);
                    } else {
                        callback(null, qrcodeURL, wallURL);
                    }
                });
            }, (qrcodeURL, wallURL) => {
                if (qrcodeURL) {
                    parentCallback({
                        success: true,
                        data: {
                            qrcode_url: qrcodeURL,
                            wall_url: wallURL
                        }
                    });
                } else {
                    me.rollbackTrialUser(data);
                    parentCallback({
                        success: false,
                        error: "给您发送邮件时出现问题，请使用可用的邮箱地址！"
                    });
                }
            }
        ]);
    }

    rollbackTrialUser(data) {
        console.log(data);
    }

    /**
     * Get trial wall info by open_id
     * 
     * 
     * @name getTrialWallInfoByOpenId
     * @param {String} appId - The appId of WeChat official account
     * @param {String} openId - The openId of WeChat user
     * @param {String} wallId - The trial wall id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     * @memberOf WallService
     */
    getTrialWallInfoByOpenId(appId, openId, wallId, parentCallback) {
        var me = this;
        var wallData = {};
        async.waterfall([
            (callback) => {
                me.getWallById(wallId, (wall) => {
                    if (wall.id === wallId) {
                        wallData.name = wall.name;
                        wallData.description = wall.description;
                        callback(null, wall);
                    } else {
                        //TODO: add log here
                        parentCallback({})
                    }
                });
            },
            (wall, callback) => {
                me.wechatService.getWeChatUserInfoByAppId(appId, openId, 'oauth', (user) => {
                    callback(null, wall, user);
                });
            }, (wall, user, callback) => {
                if (user.openid) {
                    var userInfo = {};
                    userInfo.open_id = openId;
                    userInfo.province = user.province || '未知';
                    userInfo.city = user.city || '未知';
                    userInfo.country = user.country || '未知';
                    userInfo.profile_image = user.headimgurl;
                    userInfo.gender = user.sex || 1;
                    userInfo.name = user.nickname || '匿名用户(微信)';
                    userInfo.privilege = user.privilege || '';
                    userInfo.union_id = user.unionid;
                    userInfo.wall_id = wallId;
                    userInfo.is_anonymous = !user.nickname;
                    callback(null, wall, userInfo);
                } else {
                    parentCallback({});
                }
            }, (wall, userInfo, callback) => {
                wallData.user_name = userInfo.name;
                me.saveOrUpdateWallUser(userInfo, function (id) {
                    callback(null, wall, id);
                });
            }, (wall, wallUserId, callback) => {
                if (!wall.administrators) {
                    wall.administrators = [];
                    wall.administrators.push(wallUserId);
                    callback(null, wall, wallUserId, true)
                } else if (wall.administrators && wall.administrators.length === 0) {
                    wall.administrators.push(wallUserId);
                    callback(null, wall, wallUserId, true);
                } else {
                    callback(null, wall, wallUserId, false);
                }
            }, (wall, wallUserId, needUpdateWall, callback) => {
                if (needUpdateWall) {
                    wallData.message = "您已经被设置为管理员！";
                    me.saveOrUpdateWall(wall, function (id) {

                    });
                }
                callback(null, wall, wallUserId);
            }, (wall, wallUserId) => {
                me.getPhotosByWallId(wall.id, {
                    mode: 'latest'
                }, (photos) => {
                    if (photos.length > 0) {
                        var index = Math.floor(Math.random() * photos.length);
                        wallData.photo = photos[index].photo_url;
                        parentCallback(wallData);
                    } else {
                        parentCallback(wallData);
                    }
                })
            }
        ])
    }

    /**
     * Create wall for the users which don't have wechat official accounts
     * 
     * @name createWallForNewWeChatUser
     * @param {String} userId - The user id
     * @param {String} appId - The official account's app_id
     * @param {String} openId - The open id from the scan/subscribe event
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    createWallForNewWeChatUser(userId, appId, openId, parentCallback) {
        var me = this;

        async.waterfall([
            (callback) => {
                me.getWallsByUserId(userId, (walls) => {
                    if (walls.length > 0) {
                        parentCallback({
                            success: true,
                            data: {
                                id: walls[0].id,
                                message: '您好，请点击链接访问您的跨屏朋友圈'
                            }
                        });
                    } else {
                        callback(null);
                    }
                });
            },
            (callback) => {
                me.wechatService.getWeChatUserInfoByAppId(appId, openId, 'oauth', (user) => {
                    callback(null, user);
                });
            },
            (user, callback) => {
                let wallData = {
                    name: '跨屏朋友圈',
                    app_id: appId,
                    description: '跨屏朋友圈是一款内容互动应用，可以将品牌的内容与用户通过不同的方式互动起来，有趣而生动。',
                    user_id: userId,
                    logo: 'http://cdn2.parllay.cn/2016111522/1e1m9bhdcitcw.jpeg',
                    qr_code: 'http://cdn2.parllay.cn/2016111522/17q5b2g98m1so.png',
                    administrators: [],
                    sources: [{
                        social_id: appId,
                        type: 'wechat'
                    }]
                };
                me.saveOrUpdateWall(wallData, (wall) => {
                    callback(null, wall, user);
                });
            },
            (wall, user, callback) => {
                var userInfo = {};
                userInfo.open_id = openId;
                userInfo.province = user.province || '未知';
                userInfo.city = user.city || '未知';
                userInfo.country = user.country || '未知';
                userInfo.profile_image = user.headimgurl;
                userInfo.gender = user.sex || 1;
                userInfo.name = user.nickname || '匿名用户(微信)';
                userInfo.privilege = user.privilege || '';
                userInfo.union_id = user.unionid;
                userInfo.wall_id = wall.data.id;
                userInfo.is_anonymous = !user.nickname;
                me.saveOrUpdateWallUser(userInfo, (id) => {
                    callback(null, id, wall.data.id);
                });
            },
            (wallUserId, wallId, callback) => {
                let wall = {
                    id: wallId,
                    administrators: []
                };
                wall.administrators.push(wallUserId);
                wall.description = '跨屏朋友圈是一款内容互动应用，可以将品牌的内容与用户通过不同的方式互动起来，有趣而生动。';
                me.saveOrUpdateWall(wall, (wall) => {
                    if (wall.success) {
                        callback(null, wall.data.id);
                    } else {
                        parentCallback({
                            success: false,
                            error: wall.error
                        });
                    }
                });
            },
            (wallId) => {
                parentCallback({
                    success: true,
                    data: {
                        id: wallId,
                        message: '您好，请点击链接访问您的跨屏朋友圈'
                    }
                });
            }
        ]);
    }

    /**
     * Get wall user profile by userId
     * 
     * @name getWallUserProfileWithAssets
     * @param {String} wallUserId - the user id of wall
     * 
     * @returns {void}
     */
    //TODO: Need rewrite the fucking shit code
    getWallUserProfileWithAssets(wallUserId, parentCallback) {
        var me = this;
        async.parallel({
            photo_url: function (callback) {
                me.WallPhoto.findPhotoByCreatorId(wallUserId, function (error, result) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '系统繁忙，请稍后再试！'
                        })
                    } else {
                        var doc = [];
                        for (var i = 0; i < result.length; i++) {
                            doc[i] = {
                                id: result[i].id,
                                photo_url: result[i].photo_url,
                                status: result[i].status
                            };
                        }
                        callback(null, doc)
                    }
                });
            },
            user: function (callback) {
                me.WallUser.findWallUserById(wallUserId, function (error, result) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '系统繁忙，请稍后再试！'
                        })
                    } else {
                        callback(null, result)
                    }
                });
            }
        }, function (error, result) {
            if (error) {
                parentCallback({
                    success: false,
                    code: 500,
                    error: '系统繁忙，请稍后再试！'
                })
            } else {
                parentCallback({
                    success: true,
                    code: 200,
                    data: result
                })
            }
        })
    }

    /**
     * Save asset's comment
     * 
     * @name saveAssetComment
     * @param {Object} submitData - The comment data
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    //TODO: Need rewrite
    saveAssetComment(submitData, parentCallback) {
        var me = this;
        var _comment = new me.Comment({
            photo_id: submitData.asset_id,
            content: submitData.comment.content,
            user: {
                id: submitData.user_id,
                name: submitData.name,
                profile_image: submitData.profile_image
            }
        });
        _comment.save(function (error, result) {
            if (error) {
                parentCallback({
                    success: false,
                    code: 500,
                    error: '系统繁忙，请稍后再试!'
                });
            } else {
                parentCallback({
                    success: true,
                    code: 200,
                    data: result
                });
            }
        });
    }

    /**
     * Get comments by asset id
     * 
     * @name getCommentsByAssetId
     * @param {String} assetId - The asset id
     * @param {Object} params - The query parameters
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getCommentsByAssetId(assetId, params, parentCallback) {
        this.Comment.findCommentsByPhotoId(assetId, params, function (err, result) {
            if (err) {
                parentCallback({
                    success: false,
                    code: 500,
                    error: '系统繁忙，请稍后再试!'
                })
            } else {
                parentCallback({
                    success: true,
                    code: 200,
                    data: result
                })
            }
        });
    }

    /**
     * Get wall user's profile information
     * 
     * @name getWallUserProfile
     * @param {String} wallUserId - The user id of wall
     * @param {Function} parentCallback
     * 
     * @returns {void}
     */
    //TODO: Need to rewrite
    getWallUserProfile(wallUserId, parentCallback) {
        var me = this;
        me.WallUser.findWallUserById(wallUserId, function (error, result) {
            if (error) {
                parentCallback({
                    success: false,
                    code: 500,
                    error: '系统繁忙，请稍后再试!'
                })
            } else {
                parentCallback({
                    success: true,
                    code: 200,
                    data: result
                })
            }
        })
    }

    /**
     * Update wall user's profile information
     * 
     * @name updateWallUserProfile
     * @param {String} wallUserId - The wall user's id
     * @param {Object} data - The profile data need to update
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    //TODO: Need to rewrite
    updateWallUserProfile(wallUserId, data, parentCallback) {
        var me = this;
        if (data.name) {
            me.WallUser.update({ _id: wallUserId }, { name: data.name }, function (error) {
                if (error) {
                    parentCallback({
                        success: false,
                        code: 500,
                        error: '系统繁忙，请稍后再试!'
                    })
                } else {
                    parentCallback({
                        success: true,
                        code: 200,
                        data: 'ok'
                    })
                }
            })
        }
        if (data.profile_image) {
            var profile_image = utils.saveTempFile(data.profile_image);
            var bucket = me.Context.getQiniuBucket();
            bucket.putFile(profile_image.fileKey, profile_image.realFile, function (error, reply) {
                if (error) {
                    data.profile_image = null;
                } else {
                    data.profile_image = me.getCDNImageURL(profile_image.fileKey);
                }
                me.WallUser.update({ _id: wallUserId }, { profile_image: data.profile_image }, function (error) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '更新失败'
                        })
                    } else {
                        parentCallback({
                            success: true,
                            code: 200,
                            date: 'ok'
                        })
                    }
                })
            });
        }
    }
    /**
     * Change play mode
     */
    //TODO: Need to remove in the future
    changeMode(wallId, mode, parentCallback) {
        this.Wall.update({ _id: wallId }, { mode: mode }, function (error) {
            if (error) {
                parentCallback({
                    success: false,
                    code: 400,
                    error: '系统繁忙，请稍后再试！'
                })
            } else {
                parentCallback({
                    success: true,
                    code: 500,
                    data: 'ok'
                })
            }
        })
    }

    /**
     * Remove comment by id
     * 
     * @name removeCommentById
     * @param {String} assetId - The asset id
     * @param {String} commentId - The comment id
     */
    //TODO: Need to rewrite
    removeCommentById(commentId, parentCallback) {
        var me = this;
        me.Comment.remove({ _id: commentId }, function (error) {
            if (error) {
                parentCallback({
                    success: false,
                    code: 500,
                    error: '系统繁忙，请稍后再试！'
                })
            } else {
                parentCallback({
                    success: true,
                    code: 200,
                    data: 'ok'
                })
            }
        })
    }

    /**
     * Get web asset by id
     * 
     * @name getAssetById
     * @param {String} assetId - The asset id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    //TODO: need to refactor the bloody shit code
    getAssetById(assetId, parentCallback) {
        var me = this;
        me.WallPhoto.findPhotoById(assetId, function (error, result) {
            if (error) {
                parentCallback({
                    success: false,
                    code: 500,
                    error: '系统繁忙，请稍后再试！'
                })
            } else {
                me.AssetTag.findByAssetId(assetId, function (error, data) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '系统繁忙，请稍后再试！'
                        })
                    } else {
                        result.asset_tag = data.asset_tag;
                        parentCallback({
                            success: true,
                            code: 200,
                            data: result
                        })
                    }
                });
            }
        })
    }

    /**
     * Update asset by id
     * 
     * @name updateAssetById
     * @param {String} wallId - The wall id
     * @param {String} assetId - The asset id
     * @param {Object} data - The data need to update
     * @param {userId} userId - The user id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    //TODO: Need to refactor the bloody shit code
    updateAssetById(wallId, assetId, data, userId, parentCallback) {
        var me = this;
        var functionChain = [];
        if (data.additional_url) {
            functionChain.push(function (callback) {
                var conditions = {};
                if (data.update_all_links === "on") {
                    conditions.wall_id = wallId;
                } else {
                    conditions._id = assetId;
                }
                me.WallPhoto.find(conditions, function (error, result) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '更新失败'
                        });
                    } else {
                        result.forEach(function (photo) {
                            photo.additional_url = data.additional_url;
                            photo.save(function (error, doc) {
                                //TODO: add log here if error
                            });
                        });
                        callback(null);
                    }
                });
            })
        } else {
            functionChain.push(function (callback) {
                me.WallPhoto.update({ _id: assetId }, { additional_url: '' }, function (error) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '更新失败'
                        })
                    } else {
                        callback(null);
                    }
                })
            })
        }
        if (data.description) {
            functionChain.push(function (callback) {
                me.WallPhoto.update({ _id: assetId }, { description: data.description }, function (error) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '更新失败'
                        })
                    } else {
                        callback(null);
                    }
                })
            })
        } else {
            functionChain.push(function (callback) {
                me.WallPhoto.update({ _id: assetId }, { description: '' }, function (error) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '更新失败'
                        })
                    } else {
                        callback(null);
                    }
                })
            })
        }
        if (data.tag) {
            functionChain.push(function (back) {
                me.Tag.findTagByWallId(wallId, function (error, result) {
                    var functionArray = [];
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '更新失败'
                        });
                    } else {
                        if (result.length > 0) {
                            functionArray.push(function (callback) {
                                for (var i = 0; i < result.length; i++) {
                                    if (result[i].tag == data.tag) {
                                        callback(null, 'yes', result[i].id);
                                        break;
                                    }
                                    if (i == result.length - 1) {
                                        callback(null, 'no', result[i].id);
                                        break;
                                    }
                                }
                            });
                            functionArray.push(function (ok, objId, callback) {
                                if (ok == 'yes') {
                                    callback(null, ok, objId);
                                } else {
                                    var tag = new me.Tag({
                                        wall_id: wallId,
                                        tag: data.tag
                                    });
                                    tag.save(function () {
                                        me.AssetTag.findByAssetId(assetId, function (error, doc) {
                                            if (doc.length > 0) {
                                                me.AssetTag.update({ _id: doc[0]._id }, { tag_id: tag.id }, function (error) {
                                                    callback(null, 'A', doc[0].tag_id);
                                                });
                                            } else {
                                                var asset_tag = new me.AssetTag({
                                                    asset_id: assetId,
                                                    tag_id: tag.id
                                                });
                                                asset_tag.save(function () {
                                                    callback(null, 'A', 'B');
                                                })
                                            }
                                        });
                                    });
                                }
                            });
                            functionArray.push(function (ok, objId, callback) {
                                if (ok == 'A') {
                                    callback(null, objId);
                                } else {
                                    me.AssetTag.findByAssetId(assetId, function (error, data1) {
                                        if (data1.length > 0) {
                                            me.AssetTag.update({ asset_id: data1[0].asset_id }, { tag_id: objId }, function (error) {
                                                callback(null, data1[0].tag_id);
                                            });
                                        } else {
                                            var asset_tag = new me.AssetTag({
                                                asset_id: assetId,
                                                tag_id: objId
                                            });
                                            asset_tag.save(function () {
                                                callback(null, 'B');
                                            });
                                        }
                                    })
                                }
                            });
                            functionArray.push(function (id1, callback) {
                                if (id1 && (id1 != 'B')) {
                                    me.AssetTag.findTagByTagId(id1, function (error, data2) {
                                        if (data2.length > 0) {
                                            back();
                                        } else {
                                            me.Tag.remove({ _id: id1 }, function (error, doc) {
                                                back();
                                            });
                                        }
                                    });
                                } else {
                                    back();
                                }
                            });
                            async.waterfall(functionArray);
                        } else {
                            var tag = new me.Tag({
                                wall_id: wallId,
                                tag: data.tag
                            });
                            tag.save(function () {
                                var asset_tag = new me.AssetTag({
                                    asset_id: assetId,
                                    tag_id: tag.id
                                });
                                asset_tag.save(function (error) {
                                    if (error) {
                                        parentCallback({
                                            success: false,
                                            code: 500,
                                            error: '更新失败'
                                        })
                                    } else {
                                        back();
                                    }
                                });
                            });
                        }
                    }
                });

            });
        } else {
            functionChain.push(function (callback) {
                me.AssetTag.findByAssetId(assetId, function (error, data) {
                    if (data.length == 1) {
                        me.AssetTag.findTagByTagId(data[0].tag_id, function (error, result) {
                            if (result.length > 1) {
                                me.AssetTag.remove({ asset_id: assetId }, function () {
                                    callback(null);
                                })
                            } else {
                                me.AssetTag.remove({ asset_id: assetId }, function () {
                                    me.Tag.remove({ _id: data[0].tag_id }, function () {
                                        callback(null);
                                    });
                                });
                            }
                        })
                    } else {
                        callback(null);
                    }
                });
            })
        }
        functionChain.push(function () {
            parentCallback({
                success: true,
                code: 200,
                data: '更新成功'
            })
        });
        async.waterfall(functionChain)
    }

    /**
     * Save the information from request a demo
     * 
     * @name saveDemoRequest
     * @param {Object} data - The request information
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    //TODO: Need to rewrite
    saveDemoRequest(data, parentCallback) {
        var me = this;
        async.waterfall([
            function (callback) {
                var doc = new me.DemoRequest(data);
                doc.save(function (error) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '系统繁忙，请稍后重试！'
                        })
                    } else {
                        callback(null, doc._id)
                    }
                });
            },
            function (requestId, callback) {
                me.Sales.getSalesCount(function (error, count) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '系统繁忙，请稍后再试！'
                        })
                    } else {
                        callback(null, requestId, count);
                    }
                });
            },
            function (requestId, count, callback) {
                var random = Math.floor(Math.random() * count);
                me.Sales.findRandomData(random, function (error, result) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: '系统繁忙，请稍后再试！'
                        })
                    } else {
                        var _html = "<span>From:</span>" + data.company +
                            "<p>Message Body：</p>" +
                            "<span>姓名：</span>" + data.name +
                            "<br />" + " <span>手机：</span>" + data.telephone +
                            "<br />" + "<span>邮箱：</span>" + data.email +
                            "<br />" + "<span>所属行业：</span>" + data.industry;
                        var subject = "跨屏朋友圈试用申请";
                        var options = {
                            cc: me.sendcloudConfig.cc
                        };
                        var sc = new sendcloud(me.sendcloudConfig.api_user, me.sendcloudConfig.api_key, 'norelay@parllay.cn', '跨屏产品团队');
                        sc.send(result[0].email, subject, _html, options).then(function (info) {
                            if (info.message == 'success') {
                                me.DemoRequest.update({ _id: requestId }, { status: 'sent', sales_id: result[0].id }, function (error) {
                                    if (error) {
                                        parentCallback({
                                            success: false,
                                            code: 500,
                                            error: '系统繁忙，请稍后在试！'
                                        })
                                    } else {
                                        parentCallback({
                                            success: true,
                                            code: 200,
                                            data: 'ok'
                                        })
                                    }
                                });
                            }
                        });
                    }
                })
            }
        ]);
    }

    /**
     * Get sales by unique identifier
     * 
     * @name getSalesByIdentifier
     * @param {String} salesId - The sales unique identifier
     * 
     */
    getSalesByIdentifier(salesId, parentCallback) {
        var me = this;
        me.Sales.findSalesBySalesId(salesId, function (error, result) {
            if (error) {
                parentCallback({
                    success: false,
                    code: 500,
                    error: '系统繁忙，请稍后再试'
                })
            } else {
                parentCallback({
                    success: true,
                    code: 200,
                    data: result
                })
            }
        })
    }

    /**
     * Verify the password by password
     * 
     * @name verifyPassword
     * @param {String} email - The email address
     * @param {String} password - The password
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    verifyPassword(email, password, parentCallback) {
        var me = this;
        async.waterfall([
            (callback) => {
                me.User.findUserByEmail(email.toLowerCase(), callback);
            },
            (result, callback) => {
                if (result) {
                    if (result.length === 0) {
                        parentCallback({
                            success: false,
                            code: 400,
                            error: 'login.errors.wrong_email_or_password'
                        });
                    } else {
                        var user = result[0];
                        if (user.password === password) {
                            callback(null, user);
                        } else {
                            parentCallback({
                                success: false,
                                error: 'login.errors.wrong_email_or_password'
                            });
                        }
                    }
                } else {
                    parentCallback({
                        success: false,
                        error: 'global.errors.system_busy'
                    });
                }
            },
            (user, callback) => { //Add failover logic for get started page
                let data = {
                    success: true,
                    data: {
                        id: user.id,
                        name: user.name,
                        profile_image: user.profile_image,
                        hash_id: user.hash_id,
                        qr_code: user.qr_code,
                        ref: user.ref
                    }
                };
                if (user.qr_code) {
                    parentCallback(data);
                } else {
                    me.getWallsByUserId(user.id, (result) => {
                        if (result.length > 0) {
                            parentCallback(data);
                        } else {
                            callback(null, user, data);
                        }
                    });
                }
            },
            (user, data) => {

                me.wechatService.createQRCode(me.wechatConfig.managed_app_id, {
                    is_persist: true,
                    value: `nuser:${user.id}`
                }, (result) => {
                    if (result) {
                        data.data.qr_code = me.wechatService.getQRCodeURL(result.ticket);
                        user.set({
                            qr_code: me.wechatService.getQRCodeURL(result.ticket)
                        });
                        user.save();
                    } else {
                        data.data.qr_code = '';
                    }

                    parentCallback(data);
                });
            }
        ]);
    }

    /**
     * Save or update the user information
     * 
     * @name saveOrUpdateSystemUser
     * @param {Number} createdBy - The user to create this wall user
     * @param {Number} defaultOrderId - The default order id
     * @param {Number} selectedPlanId - The plan id
     * @param {String} name - The user name
     * @param {String} email - The user email
     * @param {String} password - The user password
     * @param {Array} refs - The refs for current user
     * @param {String} locale - The default locale for user
     * @param {Object} thirdPartyId - The third party id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    saveOrUpdateSystemUser(createdBy, defaultOrderId, selectedPlanId, name, email, password, refs, locale, thirdPartyId, parentCallback) {
        var me = this;
        async.waterfall([
            function (callback) {
                me.User.findUserByEmail(email, callback)
            },
            function (result) {
                if (result) {
                    if (result.length > 0) {
                        parentCallback({
                            success: false,
                            data: { //TODO: need to refactor, this is not a good way
                                id: result[0].id,
                                name: name,
                                email: email.toLowerCase(),
                                profile_image: result[0].profile_image,
                                hash_id: result[0].identifier,
                                ref: result[0].ref
                            },
                            error: '该邮箱已经注册，请使用其他邮箱注册！'
                        });
                    } else {
                        var identifier = utils.generateIdentifier();
                        var ref = refs && refs.length > 0 && refs[refs.length - 1] || 'direct';
                        var user = new me.User({
                            name: name,
                            email: email,
                            password: password,
                            hash_id: identifier,
                            locale: locale,
                            third_party_id: thirdPartyId,
                            ref: ref
                        });
                        user.save(function (error, user) {
                            if (error) {
                                me.logger.error('Error happened when signup: ', error);
                                parentCallback({
                                    success: false,
                                    error: '系统繁忙，请稍后再试!'
                                });
                            } else {
                                let date = user.created_date.toString();
                                me.mailService.addMemberToMailchimpList(null, email, 'subscribed', {
                                    "FNAME": name,
                                    "MMERGE3": `${ref}:register`,
                                    "MMERGE4": date
                                }, (error, result) => {
                                    //me.logger.info(result);
                                });
                                me.userService.generateUserPlan(defaultOrderId, user.id, selectedPlanId, 10, 'yearly', false, createdBy, (result) => {
                                    if (result.success) {
                                        me.wechatService.createQRCode(me.wechatConfig.managed_app_id, {
                                            is_persist: true,
                                            value: `nuser:${user.id}`
                                        }, (data) => {
                                            if (!data) {
                                                user.qr_code = ''; //me.wechatService.getQRCodeURL(data.ticket);
                                            } else {
                                                user.qr_code = me.wechatService.getQRCodeURL(data.ticket)
                                            }
                                            parentCallback({
                                                success: true,
                                                data: {
                                                    id: user.id,
                                                    name: name,
                                                    email: email,
                                                    profile_image: user.profile_image,
                                                    hash_id: identifier,
                                                    qr_code: user.qr_code,
                                                    ref: ref,
                                                }
                                            });
                                        });
                                    } else {
                                        parentCallback({
                                            success: false,
                                            error: '系统繁忙，请稍后再试!'
                                        });
                                    }
                                });
                            }
                        });
                    }
                } else {
                    parentCallback({
                        success: false,
                        error: '系统繁忙，请稍后再试！'
                    })
                }
            }
        ]);
    }

    /**
     * Get managed official accounts by user id
     * 
     * @name getOfficialAccountsByUserId
     * @param {String} userId - The user id
     * @param {Function} parentCallback - The callback function
     * @param {Boolean} full - If get the full data or not
     */
    getOfficialAccountsByUserId(userId, parentCallback, full) {
        this.ManagedOfficialAccount.findAccountsByUserId(userId, function (error, result) {
            if (result && result.length > 0) {
                var retValue = [];
                for (var i = 0; i < result.length; i++) {
                    var record = {
                        id: result[i].id,
                        app_id: result[i].app_id,
                        name: result[i].name,
                        logo: result[i].logo,
                        service_type: result[i].service_type,
                        verify_type: result[i].verify_type
                    };
                    if (full) {
                        record.refresh_token = result[i].refresh_token;
                        record.access_token = result[i].access_token;
                        record.type = result[i].type;
                    }
                    retValue.push(record);
                }
                parentCallback(retValue);
            } else {
                parentCallback([]);
            }
        });
    }

    /**
     * Get all managed official accounts
     * 
     * @name getAllOfficialAccounts
     * @param {Function} parentCallback - The callback function
     * @param {Boolean} full - If get the full information or not
     * 
     * @returns {void}
     */
    getAllOfficialAccounts(parentCallback, full) {
        this.ManagedOfficialAccount.findAllAccounts(function (error, result) {
            if (result && result.length > 0) {
                var accounts = {};
                var retValue = [];
                for (var i = 0; i < result.length; i++) {
                    var record = {
                        id: result[i].id,
                        app_id: result[i].app_id,
                        name: result[i].name,
                        logo: result[i].logo
                    };
                    if (full) {
                        record.refresh_token = result[i].refresh_token;
                        record.access_token = result[i].access_token;
                        record.type = result[i].type;
                    }
                    if (!accounts[record.app_id]) {
                        accounts[record.app_id] = true;
                        retValue.push(record);
                    }
                }
                parentCallback(retValue);
            } else {
                parentCallback([]);
            }
        });
    }

    /**
     * Get official account information by application id
     * 
     * @name getAccountByAppId1
     * @param {String} appId - The application id
     * @param {Function} parentCallback - The callback function
     * @param {Boolean} full - If get the full information or not
     * 
     * @returns {void}
     */
    getAccountByAppId1(appId, type, parentCallback, full) {
        this.ManagedOfficialAccount.findAccountByAppId1(appId, type, function (error, result) {
            if (result && result.length > 0) {
                var record = {
                    id: result[0].id,
                    app_id: result[0].app_id,
                    name: result[0].name,
                    logo: result[0].logo,
                    service_type: result[0].service_type,
                    verify_type: result[0].verify_type,
                    type: result[0].type
                };
                if (full) {
                    record.refresh_token = result[0].refresh_token;
                    record.access_token = result[0].access_token;
                }
                parentCallback(record);
            } else {
                parentCallback({});
            }
        });
    }

    /**
     * Save or update official account information
     * 
     * @name saveOrUpdateAccount
     * @param {String} userId - The user id
     * @param {Object} account - The account information
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    saveOrUpdateAccount(userId, account, parentCallback) {
        var me = this;
        async.waterfall([
            (callback) => {
                me.ManagedOfficialAccount.findAccountByAppId1(account.app_id, account.social_type, function (error, result) {
                    if (result && result.length > 0) {
                        var accountObj = result[0];
                        if (accountObj.user_id !== userId) {
                            return parentCallback("此账号已经被托管到其他人的账号，无法被托管到当前账号", null);
                        }
                        accountObj.name = account.name;
                        accountObj.logo = account.logo;
                        accountObj.refresh_token = account.refresh_token;
                        accountObj.access_token = account.access_token;
                        accountObj.qr_code = account.qr_code;
                        accountObj.service_type = account.service_type;
                        accountObj.verify_type = account.verify_type;
                        accountObj.type = account.type;
                        accountObj.social_type = account.social_type;
                        account.id = accountObj.id;
                        accountObj.save();
                        parentCallback(null, account);
                    } else {
                        var accountObj = new me.ManagedOfficialAccount(account);
                        accountObj.save();
                        account.id = accountObj.id;
                        account.logo = accountObj.logo;
                        parentCallback(null, account);
                    }
                    callback(null, account);
                });
            },
            (account, callback) => {
                me.getWallsByUserId(userId, (walls) => {
                    callback(null, walls, account);
                });
            },
            (walls, account) => {
                if (walls.length === 0) {
                    me.saveOrUpdateWall({
                        name: account.name,
                        app_id: account.app_id,
                        logo: account.logo,
                        user_id: account.user_id,
                        description: account.name,
                    }, (result) => {
                        me.logger.info(`Created one wall for ${account.app_id}`);
                    });
                }
            }
        ]);
    }

    /**
     * Update the offical account's authentication information
     * 
     * @name updateAccountAuthInfo
     * @param {String} appId - The application id
     * @param {String} accessToken - The offical account's access_token
     * @param {String} refreshToken - The offical account's refresh_token
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    updateAccountAuthInfo(appId, accessToken, refreshToken, parentCallback) {
        this.ManagedOfficialAccount.findAccountByAppId1(appId, 'wechat', function (error, result) {
            if (result && result.length > 0) {
                result.forEach(function (account) {
                    account.set({
                        access_token: accessToken,
                        refresh_token: refreshToken
                    });
                    account.save();
                });

                parentCallback(result[0].id);
            } else {
                //TODO: Add log here
                parentCallback(null);
            }
        });
    }

    /**
     * Save or update wall information
     * 
     * @name saveOrUpdateWall
     * @param {Object} data - The wall data
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    //Need to refactor
    saveOrUpdateWall(data, parentCallback) {
        var wallId = data.id;
        var functionChain = [];
        var me = this;
        if (data.template) {
            data.template = escape(data.template);
        } else {
            delete data.template
        }

        if (data.logo_server_id || data.qrCode_server_id) {
            functionChain.push((callback) => {
                me.acquireStorageClients({
                    redis: true
                }, (clients) => {
                    var redis = clients.redis;
                    var clientAuthKey = redisKey.client_auth(data.app_id);
                    redis.hgetall(clientAuthKey, (error, authInfo) => {
                        if (error) {
                            me.reportError(utils.buildRedisError(error, data));
                            me.logger.error("Failed to get app auth info", error);
                            return parentCallback({
                                success: false,
                                error: '系统繁忙，请稍后再试!'
                            });
                        }

                        if (!authInfo) {
                            return parentCallback({
                                success: false,
                                error: '系统繁忙，请稍后再试!'
                            });
                        }
                        me.releaseStorageClients(clients);
                        callback(null, authInfo);
                    });
                }, (error) => {
                    me.reportError(utils.buildRedisError(error, data));
                    parentCallback({
                        success: false,
                        error: '系统繁忙，请稍后再试!'
                    });
                });
            });

            if (data.logo_server_id) {
                functionChain.push((authInfo, callback) => {
                    WeChatAPI.downloadMediaById(authInfo.token, data.logo_server_id, (fileObj) => {
                        if (fileObj) {
                            var bucket = me.Context.getQiniuBucket();
                            bucket.putFile(fileObj.fileKey, fileObj.realFile, function (error, reply) {
                                if (error) {
                                    parentCallback({
                                        success: false,
                                        error: '服务暂时不可用，请稍后再试'
                                    });
                                } else {
                                    data.logo = me.getCDNImageURL(fileObj.fileKey);
                                    callback(null, authInfo);
                                }
                            });
                        } else {
                            parentCallback({
                                success: false,
                                error: '服务暂时不可用，请稍后再试'
                            });
                        }
                    });
                });
            }

            if (data.qrCode_server_id) {
                functionChain.push((authInfo, callback) => {
                    WeChatAPI.downloadMediaById(authInfo.token, data.qrCode_server_id, (fileObj) => {
                        if (fileObj) {
                            var bucket = me.Context.getQiniuBucket();
                            bucket.putFile(fileObj.fileKey, fileObj.realFile, function (error, reply) {
                                if (error) {
                                    parentCallback({
                                        success: false,
                                        error: '服务暂时不可用，请稍后再试'
                                    });
                                } else {
                                    data.qr_code = me.getCDNImageURL(fileObj.fileKey);
                                    callback(null, authInfo);
                                }
                            });
                        } else {
                            parentCallback({
                                success: false,
                                error: '服务暂时不可用，请稍后再试'
                            });
                        }
                    });
                });
            }
        }

        if (!data.logo) {
            delete data.logo;
        }

        if (!data.qrCode) {
            delete data.qrCode;
        } else if (data.qrCode) {
            data.qr_code = data.qrCode;
        }
        if (!data.background) {
            delete data.background;
        }
        if (data.mode) {
            data.mode = Number(data.mode);
        } else {
            delete data.mode
        }
        if (data.user_authority) {
            data.user_authority = data.user_authority;
        } else {
            delete data.user_authority;
        }
        if (data.tag_source) {
            data.tag_source = data.tag_source;
        } else {
            delete data.tag_source;
        }
        functionChain.push(function (authInfo) {
            data.description = escape(data.description);
            if (data.id) {
                me.Wall.findWallById(data.id, function (error, wall) {
                    if (error) {
                        parentCallback({
                            success: false,
                            error: '服务器临时不可用，请稍后再试！'
                        })
                    } else {
                        data.settings = wall.settings || {};
                        if (data.is_auto_approve === "true") {
                            data.settings.is_auto_approve = true;
                        } else {
                            data.settings.is_auto_approve = false;
                        }
                        delete data.is_auto_approve;
                        if (!data.app_id) {
                            delete data.app_id;
                        }
                        if (data.sources && data.sources.length > 0) {
                            let newSources = [];
                            data.sources.forEach((source) => {
                                let sources = wall.sources.filter((src) => {
                                    return src.type === source.type;
                                });

                                if (!sources || sources && sources.length === 0) {
                                    newSources.push(source);
                                }
                            });

                            wall.sources.forEach((source) => {
                                let sources = data.sources.filter((src) => {
                                    return src.type === source.type;
                                });

                                if (sources.length > 0) {
                                    sources.forEach((src) => {
                                        newSources.push(src);
                                    });
                                }
                            });
                        }
                        delete data.id;
                        delete data.user_id; //Fixed the wall transfer issue
                        data.last_updated_date = new Date();

                        me.Wall.update({ _id: wallId }, data, (err, result) => {
                            parentCallback({
                                success: true,
                                data: {
                                    id: wallId
                                }
                            });
                        });
                        /*wall.save(function(error, newWall){
                            parentCallback({
                                success: true,
                                data: {
                                    id: newWall.id
                                }
                            });
                        });*/
                    }
                });
            } else {
                var wall = new me.Wall(data);
                wall.settings = {};
                if (data.is_auto_approve === "true") {
                    wall.settings.is_auto_approve = true;
                } else {
                    wall.settings.is_auto_approve = false;
                }
                if (data.sources && data.sources.length > 0) {
                    wall.sources = [];
                    data.sources.forEach((source) => {
                        let sources = wall.sources.find((src) => {
                            return src.type === source.type;
                        });

                        if (!sources || sources && sources.length === 0) {
                            wall.sources.push(source);
                        }
                    });
                } else {
                    wall.app_id = data.app_id || me.wechatService.managed_app_id;
                    wall.sources = [];
                    wall.sources.push({
                        type: 'wechat',
                        social_id: data.app_id
                    });
                }


                wall.save(function (error, wall) {
                    parentCallback({
                        success: true,
                        data: {
                            id: wall.id
                        }
                    });
                });
            }
        });
        async.waterfall(functionChain);
    }

    /**
     * Get walls by user id
     * 
     * @name getWallsByUserId
     * @param {String} userId - The user id
     * @param {String} appId - The application id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getWallsByUserId(userId, parentCallback) {
        var me = this;
        this.Wall.findWallsByUserId(userId, function (error, result) {
            if (result && result.length > 0) {
                var retValue = [];
                for (var i = 0; i < result.length; i++) {
                    retValue.push({
                        id: result[i].id,
                        app_id: result[i].id,
                        name: result[i].name,
                        description: unescape(result[i].description),
                        logo: result[i].logo,
                        background: result[i].background,
                        qr_code: result[i].qr_code,
                        background_color: result[i].background_color,
                        created_date: result[i].created_date.getTime(),
                        settings: result[i].settings || {},
                        sources: result[i].sources,
                        last_updated_date: result[i].last_updated_date.getTime()
                    });
                }
                var functionChain = [];
                retValue.forEach(function (wall) {
                    functionChain.push(function (callback) {
                        me.WallUser.getUserCountByWallId(wall.id, function (error, count) {
                            if (error) {
                                count = 0;
                            }
                            wall.user_count = count;
                            callback(null);
                        });
                    });
                    functionChain.push(function (callback) {
                        me.WallPhoto.getPhotoCountByWallId(wall.id, function (error, count) {
                            if (error) {
                                count = 0;
                            }
                            wall.photo_count = count;
                            callback(null);
                        });
                    });
                    functionChain.push(function (callback) {
                        me.Tag.findTagByUserId(userId, (error, tag) => {
                            if (error) {
                                parentCallback([]);
                            }
                            wall.tag_source = tag;
                            callback(null);
                        })
                    });
                    functionChain.push((callback) => {
                        me.WallRealTimeConfig.getRealTimeConfigByWallId(wall.id, true, (error, data) => { //TODO: Change this into campaings
                            wall.realtime = {
                                is_active: false,
                                stop_timestamp: Date.now()
                            };
                            if (data && data.length > 0) {
                                wall.realtime.is_active = true;
                                wall.realtime.start_timestamp = data[0].start_date.getTime();
                                wall.realtime.stop_timestamp = data[0].end_date.getTime();
                            }
                            callback(null);
                        });
                    });
                });
                functionChain.push(function () {
                    parentCallback(retValue);
                });
                async.waterfall(functionChain);
            } else {
                parentCallback([]);
            }
        });
    }

    /**
     * Get walls by user's unique identifier
     * 
     * @name getWallsByUserIdentifier
     * @param {String} identifier - The identifier
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getWallsByUserIdentifier(identifier, parentCallback) {
        var me = this;
        this.User.findUserByIdentifier(identifier, function (error, result) {
            if (result && result.length > 0) {
                var user = result[0];
                me.getWallsByUserId(user.id, parentCallback);
            } else {
                parentCallback([]);
            }
        });
    }

    /**
     * Get wall by id
     * 
     * @name getWallById
     * @param {String} wallId - The wall id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getWallById(wallId, parentCallback) {
        var me = this;
        me.Wall.findWallById(wallId, (error, record) => {
            if (record) {
                var retValue = {};
                retValue = {
                    id: record.id,
                    app_id: record.app_id, //TODO: need to remove
                    name: record.name,
                    description: record.description,
                    logo: record.logo,
                    qr_code: record.qr_code,
                    administrators: record.administrators,
                    author: record.author,
                    tag_authority: record.tag_authority, //TODO: need to remove
                    user_id: record.user_id,
                    settings: record.settings || {},
                    user_authority: record.user_authority, //TODO: need to remove
                    background: record.background,
                    background_color: record.background_color,
                    template: record.template, //TODO: need to remove
                    mode: record.mode, //TODO: need to remove
                    sources: record.sources
                };
                if (record.sources && record.sources.length > 0) {
                    let wechatSources = record.sources.filter((source) => {
                        return source.type === 'wechat';
                    });

                    retValue.app_id = wechatSources.length > 0 ? wechatSources[0].social_id : null;
                }

                retValue.app_id = retValue.app_id || me.wechatConfig.managed_app_id;
                async.waterfall([
                    function (callback) {
                        me.Tag.findTagByWallId(record.id, (error, data) => {
                            if (data && data.length > 0) {
                                var arr = [];
                                for (var k = 0; k < data.length; k++) {
                                    var innerArray = {};
                                    innerArray['id'] = String(data[k].id);
                                    innerArray['tag'] = String(data[k].tag);
                                    arr.push(innerArray);
                                }
                                retValue.tag_source = arr;
                            }
                            callback(null);
                        });
                    },
                    function (callback) {
                        me.WallRealTimeConfig.getRealTimeConfigByWallId(wallId, false, (error, data) => {
                            retValue.realtime = {
                                is_active: false,
                                stop_timestamp: Date.now()
                            };
                            if (data && data.length > 0) {
                                retValue.realtime.is_active = true;
                                retValue.realtime.stop_timestamp = data[0].end_date.getTime();
                            }
                            callback(null);
                        });
                    },
                    function () {
                        parentCallback(retValue);
                    }
                ]);
            } else {
                parentCallback({});
            }
        });
    }

    /**
     * Save wall realtime configuration
     * 
     * @name saveWallRealTimeConfig
     * @param {Number} orderId - The purchase order id
     * @param {String} wallId - The wall id
     * @param {String} startDate - The start date
     * @param {String} endDate - The end date
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    saveWallRealTimeConfig(orderId, wallId, startDate, endDate, parentCallback) {
        var realtimeConfig = new this.WallRealTimeConfig({
            order_id: orderId,
            wall_id: wallId,
            start_date: new Date(startDate),
            end_date: new Date(endDate)
        });

        realtimeConfig.save((error, config) => {
            parentCallback(config.id);
        });
    }

    /**
     * Save or update wall user
     * 
     * @name saveOrUpdateWallUser
     * @param {Object} userInfo - The user information
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    saveOrUpdateWallUser(userInfo, parentCallback) {
        var me = this;
        this.WallUser.getUserByOpenId(userInfo.open_id, userInfo.wall_id, function (error, result) {
            if (result && result.length > 0) {
                result[0].set(userInfo);
                result[0].save();
                parentCallback(result[0].id);
            } else {
                var wallUser = new me.WallUser(userInfo);
                wallUser.save(function (error, user) {
                    parentCallback(user.id);
                });
            }
        });
    }

    /**
     * Save or update wall photo
     * 
     * @name saveOrUpdateWallPhoto
     * @param {String} wallId - The wall id
     * @param {String} message - The message for photo
     * @param {String} serverId - The WeChat asset serverId 
     * @param {Number} ratio - The photo's retio
     * @param {Object} userInfo - The user information
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    saveOrUpdateWallPhoto(wallId, message, serverId, ratio, link, userInfo, parentCallback) {
        var me = this;
        var redis = null;
        async.waterfall([
            function (callback) {
                me.Context.acquireRedisClient(callback);
            }, (redisClient, callback) => {
                if (!redisClient) {
                    parentCallback({
                        success: false,
                        error: '服务暂时不可用，请稍后再试'
                    });
                } else {
                    redis = redisClient;
                    var clientAuthKey = redisKey.client_auth(userInfo.app_id);
                    redis.hgetall(clientAuthKey, callback);
                }
            }, (authInfo, callback) => {
                me.Context.releaseRedisClient(redis);
                if (!authInfo) {
                    parentCallback({
                        success: false,
                        error: '服务暂时不可用，请稍后再试'
                    });
                } else {
                    callback(null, authInfo.token);
                }
            }, (token, callback) => { // Download media from wechat server
                WeChatAPI.downloadMediaById(token, serverId, function (fileObj) {
                    if (fileObj) {
                        var bucket = me.Context.getQiniuBucket();
                        bucket.putFile(fileObj.fileKey, fileObj.realFile, function (error, reply) {
                            if (error) {
                                parentCallback({
                                    success: false,
                                    error: '服务暂时不可用，请稍后再试'
                                });
                            } else {
                                callback(null, token, me.getCDNImageURL(fileObj.fileKey));
                            }
                        });
                    } else {
                        parentCallback({
                            success: false,
                            error: '服务暂时不可用，请稍后再试'
                        });
                    }
                });
            }, (token, url, callback) => {
                me.getWallById(wallId, (wall) => {
                    var isAutoApprove = wall && wall.settings && wall.settings.is_auto_approve;
                    callback(null, token, url, isAutoApprove);
                });
            }, (token, url, auto_approve, callback) => { // Save wall photo data
                me.Wall.findWallById(wallId, (error, result) => {
                    var data = {
                        wall_id: wallId,
                        creator_id: userInfo.wall_user_id,
                        photo_url: url,
                        description: message,
                        ratio: ratio,
                        operations: [{
                            operator_id: userInfo.wall_user_id,
                            operation: 'uploaded',
                            timestamp: Date.now()
                        }],
                        asset_type: 'photo'
                    };
                    if (link) {
                        data.additional_url = link;
                    }

                    if (auto_approve) {
                        data.status = "approved";
                    }
                    data.created_timestamp = Date.now();
                    var wallPhoto = new me.WallPhoto(data);
                    wallPhoto.save();
                    if (error) {
                        return parentCallback({
                            success: false,
                            error: error
                        });
                    }
                    if(auto_approve) {
                        data.user_id = result.user_id;
                        var backupPhoto = new me.LatestUploadWallPhotos(data);
                        backupPhoto.save();
                    }
                    parentCallback({
                        success: true,
                        data: {
                            id: wallPhoto.id,
                            photo_url: url
                        }
                    });
                    callback(null, url, auto_approve);
                });

            }, (url, auto_approve) => { // Send wechat customer support message
                url = `http:${me.getCDNImageURL(url)}`;
                var articles = [];
                if (auto_approve) {
                    articles.push({
                        "title": '您的用户刚刚上传了一张图片，已经自动审核通过。',
                        "description": message || '用户没有添加描述信息',
                        "url": `http://${userInfo.host}/wall/${wallId}#auditing/approved`,
                        "picurl": url
                    })
                } else {
                    articles.push({
                        "title": '您的用户刚刚上传了一张图片，赶快点击图片去审核吧',
                        "description": message || '用户没有添加描述信息',
                        "url": `http://${userInfo.host}/wall/${wallId}#auditing/pending`,
                        "picurl": url
                    });
                }
                me.wechatMessageService.sendCustomerServiceMessageToWallAdministrators(wallId, articles);
            }
        ]);
    }

    /**
     * Get photos by wall id
     * 
     * @name getPhotosByWallId
     * @param {String} wallId - The wall id
     * @param {Object} params - The parameters for querying wall photos
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getPhotosByWallId(wallId, params, parentCallback) {
        var me = this;
        var retValue = [];
        var count = 0;
        async.waterfall([
            (callback) => {
                if (params.tag) {
                    //TODO: implement the tag filter logic here
                    /*me.AssetTag.findAssetIdsByWallId(wallId, function(error, assetIds) {
                        params.tag = result;
                        callback(null, assetIds);
                    });*/
                    callback(null, null);
                } else {
                    callback(null, null);
                }
            },
            (assetIds) => {
                params.assetsIds = assetIds;
                me.WallPhoto.findPhotosByWallId(wallId, params, (error, result) => {
                    if (result && result.length > 0) {
                        var functionChain = [];
                        result.forEach((photo, j) => {
                            var likes = photo.likes ? photo.likes.length : 0;
                            var isLiked = (photo.likes.indexOf(params.userId) >= 0);
                            var photoId = (photo.id || photo._id).toString();
                            var additionalUrl = (photo.additional_url ? (!photo.additional_url.startsWith("http://") ? "http://" + photo.additional_url : photo.additional_url) : "");
                            var midVal = {
                                id: photoId,
                                user: {
                                    id: photo.creator_id
                                },
                                photo_url: me.getCDNImageURL(photo.photo_url),
                                description: photo.description,
                                ratio: photo.ratio,
                                is_liked: isLiked,
                                likes: likes,
                                status: photo.status,
                                timestamp: photo.last_updated_date.getTime(),
                                source: photo.source,
                                additional_url: additionalUrl,
                                asset_meta_data: photo.asset_meta_data || {},
                                type: photo.asset_type
                            };
                            if (params.platform === 'console' && midVal.additional_url) {
                                midVal.statistics = {};
                                let clicks = photo.statistics && photo.statistics.clicks && photo.statistics.clicks.length || 0;
                                let likes = photo.statistics && photo.statistics.likes && photo.statistics.likes.length || 0;
                                let conversions = photo.statistics && photo.statistics.conversions && photo.statistics.conversions.length || 0;

                                midVal.statistics.clicks = clicks;
                                midVal.statistics.likes = likes;
                                midVal.statistics.conversions = conversions;
                            }
                            if (midVal.additional_url) { //Added track for third party url
                                let encodedURL = btoa(midVal.additional_url).replace(/\//g, '-');
                                midVal.additional_url = `/mkprobe/${encodedURL}/${wallId}/${midVal.id}`
                            }

                            if (midVal.source == 'PC') {
                                functionChain.push((callback) => {
                                    me.User.findUserById(midVal.user.id, (error, user) => {
                                        if (user) {
                                            midVal.user = {
                                                id: user.id,
                                                name: user.name,
                                                profile_image: me.getCDNImageURL(user.profile_image)
                                            };
                                        } else {
                                            midVal.user = {
                                                id: null,
                                                name: "匿名用户",
                                                profile_image: me.getCDNImageURL('dashboard/images/default_profile_image.png')
                                            };
                                        }
                                        retValue.push(midVal);
                                        callback(null);
                                    });
                                });
                            } else {
                                functionChain.push((callback) => {
                                    me.WallUser.findWallUserById(midVal.user.id, (error, user) => {
                                        if (user) {
                                            midVal.user.id = user.id;
                                            midVal.user.profile_image = me.getCDNImageURL(user.profile_image);
                                            midVal.user.name = user.name;
                                        } else {
                                            midVal.user = {
                                                id: null,
                                                name: "匿名用户",
                                                profile_image: me.getCDNImageURL('dashboard/images/default_profile_image.png')
                                            };
                                        }
                                        retValue.push(midVal);
                                        callback(null);
                                    });
                                });
                            }
                            functionChain.push((callback) => {
                                me.Comment.findCommentCountByPhotoId(midVal.id, (error, result) => {
                                    if (error) {
                                        parentCallback([])
                                    } else {
                                        count++;
                                        midVal.comments_count = result;
                                        callback(null);
                                    }
                                });
                            });
                        });
                        functionChain.push(function (callback) {
                            parentCallback(retValue);
                        });
                        async.waterfall(functionChain);
                    } else {
                        parentCallback([]);
                    }
                });
            }
        ]);
    }

    /**
     * 查询最新上传的数据
     */
    getLatestUploadPhotos(params, parentCallback) {
        var me = this, data;
        params.status = 'approved';
        var returnData = {};
        async.waterfall([
            (callback) => {
                me.Wall.findWallById(params.wall_id, (error, result) => {
                    if (error) {
                        return parentCallback({
                            success: false,
                            error: error
                        });
                    }
                    params.userId = result.user_id;
                    callback(null, params);
                })
            },
            (params, callback) => {
                me.LatestUploadWallPhotos.findLatestPhotosByWallId(params, (error, result) => {
                    if (result && result.length > 0) {
                        formatData(me, params, result, (error, res) => {
                            returnData.current = res;
                            callback(null, params);
                        });
                    } else {
                        returnData.current = [];
                        callback(null, params);
                    }

                });
            },
            (params, callback) => {
                me.LatestUploadWallPhotos.findNotWallIdUpload(params, (error, result) => {
                    if (result && result.length > 0) {
                        formatData(me, params, result, (error, res) => {
                            returnData.rest = res;
                            callback(null, params);
                        });
                    } else {
                        returnData.rest = [];
                        callback(null, params);
                    }

                });
            },
            (params) => {
                me.LatestUploadWallPhotos.getLatestUploadTime(params, (error, result) => {
                    if (result && result.length > 0) {
                        returnData.since = result[0].created_timestamp;
                    } else {
                        returnData.since = Date.now();
                    }
                    parentCallback(returnData);
                });
            }
        ]);

    }

    getLinkStatisticsByWallId(wallId, params, parentCallback) {
        var me = this;
        var retValue = [];
        var count = 0;

        async.waterfall([
            (callback) => {
                if (params.tag) {
                    //TODO: implement the tag filter logic here
                    /*me.AssetTag.findAssetIdsByWallId(wallId, function(error, assetIds) {
                        params.tag = result;
                        callback(null, assetIds);
                    });*/
                    callback(null, null);
                } else {
                    callback(null, null);
                }
            },
            (assetIds) => {
                params.wall_id = wallId;
                params.assetsIds = assetIds;
                me.WallPhoto.findLinkStatisticsByWallId(wallId, params, (error, result) => {
                    if (result && result.length > 0) {
                        result.forEach((photo) => {
                            let statsData = {
                                id: photo.id,
                                photo_url: me.getCDNImageURL(photo.photo_url),
                                description: photo.description,
                                additional_url: photo.additional_url,
                                statistics: {},
                                timestamp: photo.created_date.getTime()
                            };

                            let clicks = photo.statistics && photo.statistics.clicks && photo.statistics.clicks.length || 0;
                            let likes = photo.statistics && photo.statistics.likes && photo.statistics.likes.length || 0;
                            let conversions = photo.statistics && photo.statistics.conversions && photo.statistics.conversions.length || 0;

                            statsData.statistics.clicks = clicks;
                            statsData.statistics.likes = likes;
                            statsData.statistics.conversions = conversions;

                            retValue.push(statsData);
                        });

                        parentCallback(retValue);
                    } else {
                        parentCallback([]);
                    }
                });
            }
        ]);
    }

    /**
     * Do photo action
     * 
     * @name doPhotoAction
     * @param {String} action - The action on photo
     * @param {String} wallId - The wall id
     * @param {String} photoId - The photo id
     * @param {String} userId - The user id
     * @param {Object} data - The additional data
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    doPhotoAction(action, wallId, photoId, userId, data, parentCallback) {
        var me = this;

        async.waterfall([
            (callback) => {
                if (action == 'approve') {
                    // 从拒绝或者删除恢复为审核通过
                    // 向备份表里增加一条恢复的数据
                    me.WallPhoto.findPhotoById(photoId, (error, photo) => {
                        if(error) {
                            return parentCallback(null);
                        }
                        me.Wall.findWallById(wallId, (error, result) => {
                            var p = new me.LatestUploadWallPhotos({
                                _id: photo._doc._id,
                                wall_id: photo._doc.wall_id,
                                creator_id: photo._doc.creator_id,
                                user_id: result.user_id,
                                photo_url: photo._doc.photo_url,
                                description: photo._doc.description,
                                ratio: photo._doc.ratio,
                                asset_type: photo._doc.asset_type,
                                asset_meta_data: photo._doc.asset_meta_data,
                                likes: photo._doc.likes,
                                operations: photo._doc.operat,
                                additional_url: photo._doc.additional_url,
                                statistics: photo._doc.statistics,
                                source: photo._doc.source, // wechat, PC, dianping, weibo
                                status: 'approved', // pending, approved, rejected and deleted
                                last_updated_date: Date.now(),
                                created_date: Date.now(),
                                created_timestamp: Date.now()
                            });
                            p.save((error, result) => {
                                if (error) {
                                    return parentCallback(null);
                                } else {
                                    callback(null);
                                }
                            });
                        })

                    });
                } else {
                    me.LatestUploadWallPhotos.findPhotoById(photoId, (error, photo) => {
                        if (photo) {
                            me.LatestUploadWallPhotos.removePhoto(photoId, (error, result) => {
                                if (error) {
                                    parentCallback(null);
                                } else {
                                    callback(null);
                                }
                            });
                        } else {
                            callback(null);
                        }
                    });

                }

            },
            () => {
                me.WallPhoto.findPhotoById(photoId, (error, photo) => {
                    if (photo) {
                        changeStatus(me, userId, action, photo, parentCallback);
                    } else {
                        parentCallback(null);
                    }
                });
            }
        ]);
    }

    /**
     * Add wall administrator
     * 
     * @name addWallAdministrator
     * @param {String} wallId - The wall id
     * @param {String} wallUserId - The wall user id
     * @param {String} systemUserId - The wall owner id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    addWallAdministrator(wallId, wallUserId, systemUserId, parentCallback) {
        this.Wall.findWallById(wallId, function (error, wall) {
            if (wall) {
                if (wall.administrators.indexOf(wallUserId) >= 0) {
                    parentCallback({
                        success: false,
                        error: "此用户已经是管理员，无需再次添加！"
                    })
                } else {
                    var index = wall.author.indexOf(wallUserId);
                    if (index >= 0) {
                        wall.author.splice(index, 1);
                    }
                    wall.administrators.push(wallUserId);
                    wall.operations.push({
                        type: 'add_adminstrator',
                        operator_id: systemUserId,
                        data: wallUserId,
                        timestamp: Date.now()
                    });
                    wall.save(function (error, wall) {
                        if (error) {
                            parentCallback({
                                success: false,
                                error: "系统繁忙，请稍后再试！"
                            });
                        } else {
                            parentCallback({
                                success: true,
                                data: "管理员设置成功！"
                            });
                        }
                    });
                }
            } else {
                parentCallback({
                    success: false,
                    error: "找不到该照片墙！"
                });
            }
        });
    }

    getWallAdministrators(wallId, parentCallback) {
        var me = this;
        async.waterfall([
            (callback) => {
                me.getWallById(wallId, (wall) => {
                    callback(null, wall.administrators || []);
                });
            },
            (administrators, callback) => {
                if (administrators && administrators.length > 0) {
                    me.getWallUsersByWallId(wallId, {
                        ids: administrators,
                        source: 'wechat'
                    }, (result) => {
                        parentCallback(result);
                    });
                } else {
                    parentCallback([]);
                }
            }
        ])
    }

    /**
     * Remove wall administrator
     * 
     * @name removeWallAdministrator
     * @param {String} wallId - The wall id
     * @param {String} wallUserId - The wall user id
     * @param {String} systemUserId - The wall owner id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    removeWallAdministrator(wallId, wallUserId, systemUserId, parentCallback) {
        this.Wall.findWallById(wallId, function (error, wall) {
            if (wall) {
                if (wall.administrators.indexOf(wallUserId) < 0) {
                    parentCallback({
                        success: false,
                        error: "此用户不是管理员，无需取消！"
                    })
                } else {
                    if (wall.author.indexOf(wallUserId) >= 0) {
                        wall.author.splice(wall.author.indexOf(wallUserId), 1);
                    }
                    var index = wall.administrators.indexOf(wallUserId);
                    wall.administrators.splice(index, 1);
                    wall.operations.push({
                        type: 'remove_adminstrator',
                        operator_id: systemUserId,
                        data: wallUserId,
                        timestamp: Date.now()
                    });
                    wall.save(function (error, wall) {
                        if (error) {
                            parentCallback({
                                success: false,
                                error: "系统繁忙，请稍后再试！"
                            });
                        } else {
                            parentCallback({
                                success: true,
                                data: "管理员取消成功！"
                            });
                        }
                    });
                }
            } else {
                parentCallback({
                    success: false,
                    error: "找不到该照片墙！"
                });
            }
        });
    }

    /**
     * Add wall author
     * 
     * @name addWallAuthor
     * @param {String} wallId - The wall id
     * @param {String} wallUserId - The wall user id
     * @param {String} systemUserId - The wall owner id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    addWallAuthor(wallId, wallUserId, systemUserId, parentCallback) {
        var me = this;
        me.Wall.findWallById(wallId, function (error, wall) {
            if (wall) {
                var index = wall.administrators.indexOf(wallUserId);
                if (index >= 0) {
                    wall.administrators.splice(index, 1);
                }
                wall.author.push(wallUserId);
                wall.operations.push({
                    type: 'adminstrator_to_author',
                    operator_id: systemUserId,
                    data: wallUserId,
                    timestamp: Date.now()
                });
                wall.save(function (error, wall) {
                    if (error) {
                        parentCallback({
                            success: false,
                            code: 500,
                            error: "系统繁忙，请稍后再试！"
                        });
                    } else {
                        parentCallback({
                            success: true,
                            code: 200,
                            data: "管理员取消成功！"
                        });
                    }
                });
            } else {
                parentCallback({
                    success: false,
                    code: 500,
                    error: "找不到该照片墙！"
                });
            }
        });
    }

    /**
     * Remove wall author
     * 
     * @name removeWallAuthor
     * @param {String} wallId - The wall id
     * @param {String} wallUserId - The wall user id
     * @param {String} systemUserId - The wall owner id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    removeWallAuthor(wallId, wallUserId, systemUserId, parentCallback) {
        this.Wall.findWallById(wallId, function (error, wall) {
            if (wall) {
                if (wall.author.indexOf(wallUserId) < 0) {
                    parentCallback({
                        success: false,
                        error: "此用户不是作者，无需取消！"
                    })
                } else {
                    var index = wall.author.indexOf(wallUserId);
                    wall.author.splice(index, 1);
                    wall.operations.push({
                        type: 'remove_author',
                        operator_id: systemUserId,
                        data: wallUserId,
                        timestamp: Date.now()
                    });
                    wall.save(function (error) {
                        if (error) {
                            parentCallback({
                                success: false,
                                error: "系统繁忙，请稍后再试！"
                            });
                        } else {
                            parentCallback({
                                success: true,
                                data: "作者取消成功！"
                            });
                        }
                    });
                }
            } else {
                parentCallback({
                    success: false,
                    error: "找不到该照片墙！"
                });
            }
        })
    }

    /**
     * Get wall users by wall id
     * 
     * @name getWallUsersByWallId
     * @param {String} wallId - The wall id
     * @param {Object} params - The parameters for querying wall users
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getWallUsersByWallId(wallId, params, parentCallback) {
        var me = this;
        async.waterfall([
            function (callback) {
                me.Wall.findWallById(wallId, callback);
            },
            function (wall) {
                if (wall) {
                    me.WallUser.findUsersByWallId(wallId, params, function (error, result) {
                        var retValue = [];
                        if (result && result.length > 0) {
                            result.forEach(function (user) {
                                retValue.push({
                                    id: user.id,
                                    name: user.name,
                                    open_id: user.open_id,
                                    profile_image: me.getCDNImageURL(user.profile_image),
                                    location: {
                                        country: user.country,
                                        province: user.province,
                                        city: user.city
                                    },
                                    source: user.source,
                                    is_admin: (wall.administrators.indexOf(user.id) >= 0),
                                    is_anonymous: user.is_anonymous,
                                    is_author: (wall.author.indexOf(user.id) >= 0),
                                    timestamp: user.created_date.getTime()
                                });
                            });
                            parentCallback(retValue);
                        } else {
                            parentCallback(retValue);
                        }
                    });
                } else {
                    parentCallback([]);
                }
            }
        ]);
    }
    /**
     * This function is used to do some maintainance works
     * @name initializeData
     */
    initializeData() {
        this.User.getUsers(function (error, users) {
            if (users && users.length > 0) {
                var functionChain = [];
                users.forEach(function (user) {
                    functionChain.push(function (callback) {
                        user.hash_id = utils.generateIdentifier();
                        user.save(function (error, result) {
                            callback();
                        });
                    });
                });
                async.waterfall(functionChain);
            }
        });
    }

    /**
     * Ensure we generate an anonymous
     * 
     * @name ensureAnonymousUser
     * @param {Object} request - The request
     * @param {String} userId - The userId
     * @param {Function} callback - The callback function
     * 
     * @returns {void}
     */
    ensureAnonymousUser(request, userId, callback) {
        var agent = request.useragent;

        var additionInfo = (agent && agent.platform) ? "(" + agent.platform + ")" : "Unknown";
        var userInfo = {
            id: userId,
            wall_id: request.params.wall_id,
            union_id: utils.getRemoteIP(request),
            name: "匿名用户" + additionInfo,
            gender: 3,
            //TODO: 添加通过IP来获取位置的逻辑
            province: "未知",
            country: "未知",
            city: "未知",
            refresh_token: null,
            privilege: null,
            is_anonymous: true,
            source: request.session.is_wechat ? 'wechat' : 'other'
        };
        if (!userId) {
            delete userInfo.id;
            userInfo.open_id = request.session.id;
        } else {
            return callback(userId);
        }

        this.saveOrUpdateWallUser(userInfo, function (id) {
            callback(id);
        });
    }

    saveOrUpdateWallSettings(wallId, settings, parentCallback) {
        utils.checkObjectParameter(settings, 'settings');

        var me = this;

        this.Wall.findWallById(wallId, (error, wall) => {
            if (error) {
                me.logger.error('Error happened when set wall settings: ', error);
                parentCallback({
                    success: false,
                    error: '服务器临时不可用，请稍后再试！'
                });
            } else {

                wall.settings = wall.settings || {};

                for (var name in settings) {
                    wall.settings[name] = settings[name];
                }

                me.Wall.update({ _id: wallId }, { settings: wall.settings }, (error, result) => {
                    if (error) {
                        me.logger.error('Error happened when set wall settings: ', error);
                        parentCallback({
                            success: false,
                            error: '服务器临时不可用，请稍后再试！'
                        });
                    } else {
                        parentCallback({
                            success: true,
                            data: "OK"
                        });
                    }
                });
            }
        });
    }

    /**
     * This is a temporary function to upgrade user's plan
     * 
     * @name upgradePlanData
     * @param {Function} parentCallback - The callback function
     * 
     * @return {void}
     */
    upgradePlanData(parentCallback) {
        var me = this;
        async.parallel({
            mysql: function (callback) {
                me.Context.acquireMySQLClient(callback);
            }
        }, function (error, result) {
            var mysql = result.mysql;
            me.User.getUsers(function (error, users) {
                var copies = 0;
                var startDate = new Date();
                var endDate = new Date();
                endDate.setTime(startDate.getTime());
                endDate.setFullYear(endDate.getFullYear() + 1);
                var startDateStr = startDate.getFullYear() + "-" + (startDate.getMonth() + 1) + "-" + startDate.getDate();
                var endDateStr = endDate.getFullYear() + "-" + (endDate.getMonth() + 1) + "-" + endDate.getDate() + " 23:59:59";
                if (users && users.length > 0) {
                    var functionChain = [];
                    users.forEach(function (user) {

                        functionChain.push(function (callback) {
                            mysql.query('CALL sp_wall_generateUserPlan(?,?,?,?,?,?,?)', [1000, 1002, user.id, startDateStr, endDateStr, 0, 1000], function (error, result) {
                                if (result && result[0] && result[0].length > 0 && result[0][0].return_value === 1) {
                                    copies++;
                                }
                                callback(null);
                            });
                        });

                    });

                    functionChain.push(function (callback) {
                        mysql.query('UPDATE wall_sales_orders SET amount = amount + ' + (copies * 7200) + ' WHERE id = 1000', function (error, result) {
                            callback(null);
                        });
                    });

                    functionChain.push(function () {
                        me.Context.releaseMySQLClient(mysql);
                        parentCallback('Yes');
                    });

                    async.waterfall(functionChain);
                } else {
                    parentCallback('No');
                }
            });
        });
    }

    /**
     * Get managed official accounts by user id and social type
     * 
     * @name getManagedOfficialAccountsByUserIdAndType
     * @param {String} userId - The user id
     * @param {String} socialType - The social type of managed official account
     */
    getManagedOfficialAccountsByUserIdAndType(userId, socialType, parentCallback) {
        var me = this;
        var criteria = {};
        if (userId) {
            criteria.user_id = userId;
        }

        if (socialType) {
            criteria.social_type = socialType;
        }
        this.ManagedOfficialAccount.findAccountsByCriteria(criteria, (error, result) => {
            if (error) {
                me.reportError(error);
                return parentCallback({
                    success: false,
                    error: '服务器临时不可用，请稍后再试！'
                });
            }

            let accounts = [];

            result.forEach((account) => {
                accounts.push({
                    id: account.id,
                    social_id: account.app_id,
                    name: account.name,
                    logo: account.logo,
                    type: account.social_type
                });
            });

            parentCallback({
                success: true,
                data: {
                    accounts: accounts
                }
            });
        });
    }

    /**
     * Get asset statistics by asset id
     * 
     * @name getAssetStatisticsById
     * @param {String} assetId - The asset id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getAssetStatisticsById(assetId, parentCallback) {
        this.WallPhoto.findPhotoById(assetId, (error, result) => {
            if (error) {
                parentCallback({});
            } else {
                let statistics = result.statistics;
                parentCallback(statistics);
            }
        });
    }

    /**
     * Update asset statistics information
     * 
     * @name updateAssetStatistics
     * @param {String} assetId - The asset id
     * @param {String} wallUserId - The wall user's id
     * @param {String} action - The action
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    updateAssetStatistics(assetId, wallUserId, action, data, parentCallback) {
        var me = this;
        this.WallPhoto.findPhotoById(assetId, (error, asset) => {
            if (error) {
                parentCallback(0);
            } else {
                asset.statistics = asset.statistics || {};
                asset.statistics[`${action}s`] = asset.statistics[`${action}s`] || [];
                asset.statistics[`${action}s`].push({ user_id: wallUserId, timestamp: Date.now(), data: data });
                me.WallPhoto.update({ _id: asset.id }, {
                    statistics: asset.statistics
                }, (error, updated) => {
                    parentCallback(asset.statistics[`${action}s`].length);
                });
            }
        });
    }

    getPhotosByUserId(userId, params, parentCallback) {

    }

    getUserByThirdPartyId(thirdPartyId, type, parentCallback) {
        var me = this;
        this.User.findUsersByThirdPartyId(thirdPartyId, type, (error, users) => {
            if (error) {
                me.logger.error(error);
                parentCallback([]);
            } else {
                var result = [];
                users.forEach((user) => {
                    result.push({
                        id: user.id,
                        name: user.name,
                        profile_image: user.profile_image,
                        email: user.email,
                        ref: user.ref,
                        third_party_id: user.third_party_id
                    });
                });

                parentCallback(result);
            }
        });
    }

    getWallUsersByThirdPartyId(thirdPartyId, type, wallId, parentCallback) {
        var me = this;
        this.WallUser.findWallUsersByThirdPartyId(thirdPartyId, type, wallId, (error, users) => {
            if (error) {
                me.logger.error(error);
                parentCallback([]);
            } else {
                var result = [];
                users.forEach((user) => {
                    result.push({
                        id: user.id,
                        name: user.name,
                        profile_image: user.profile_image,
                        source: user.source,
                        is_anonymous: user.is_anonymous
                    });
                });

                parentCallback(result);
            }
        });
    }

    /**
     * Create trackable link for WeChat User when they send links to Billboard WeChat offical account
     * 
     * @name createTrackableLinkForWeChatUser
     * @param {String} openId - The WeChat user's open id
     * @param {String} url - The Url need to convert
     * @param {Object} data - The data when user send a Link message
     * @param {Function} parentCallback 
     */
    createTrackableLinkForWeChatUser(openId, url, data, parentCallback) {
        var me = this;
        var wallInfo = {};
        async.waterfall([
            (callback) => {
                me.getUserByThirdPartyId(openId, 'wechat-link-message', (users) => {
                    callback(null, users);
                });
            },
            (users, callback) => {
                if (users.length === 0) {
                    me.wechatService.getWeChatUserInfoByAppId(me.wechatConfig.managed_app_id, openId, 'oauth', (user) => {
                        callback(null, user, null);
                    });
                } else {
                    callback(null, null, users[0].id);
                }
            },
            (weChatUser, userId, callback) => {
                if (weChatUser) {
                    let selfServiceUserId = 2001;
                    let internalOrderId = 1000;
                    let initialPlanId = 1000;
                    let email = `${openId}@wechat.parllay.cn`;
                    let name = weChatUser.nickname || '匿名用户(微信消息)';
                    let password = utils.generateIdentifier(8, 'full');
                    let thirdPartyId = openId;
                    me.saveOrUpdateSystemUser(selfServiceUserId, internalOrderId, initialPlanId, name, email, password, ['wechat-link-message'], 'zh-CN', thirdPartyId, (result) => {
                        if (!result || !result.success) {
                            me.logger.error(result && result.error || 'Unknown error');
                            parentCallback([]);
                        } else {
                            callback(null, result.data.id, weChatUser);
                        }

                    });
                } else {
                    callback(null, userId, null);
                }
            },
            (userId, wechatUser, callback) => {
                me.getWallsByUserId(userId, (walls) => {
                    callback(null, userId, wechatUser, walls);
                });
            },
            (userId, wechatUser, walls, callback) => {
                if (walls.length === 0) {
                    wallInfo = {
                        name: wechatUser.nickname || '匿名用户(微信消息)',
                        app_id: me.wechatConfig.managed_app_id,
                        description: '跨屏朋友圈是一款内容互动应用，可以将品牌的内容与用户通过不同的方式互动起来，有趣而生动。',
                        user_id: userId,
                        logo: wechatUser.headimgurl,
                        administrators: [],
                        sources: [{
                            social_id: me.wechatConfig.managed_app_id,
                            type: 'wechat'
                        }]
                    };
                    me.saveOrUpdateWall(wallInfo, (wall) => {
                        if (wall.success) {
                            callback(null, wechatUser, wall.data.id);
                        } else {
                            parentCallback([]);
                        }
                    });
                } else {
                    wallInfo = walls[0];
                    callback(null, wechatUser, walls[0].id)
                }
            },
            (wechatUser, wallId, callback) => {
                me.getWallUsersByThirdPartyId(openId, 'wechat', wallId, (users) => {
                    callback(null, wechatUser, wallId, users);
                });
            },
            (weChatUser, wallId, users, callback) => {
                if (users.length === 0) {
                    var userInfo = {};
                    userInfo.open_id = openId;
                    userInfo.province = weChatUser.province || '未知';
                    userInfo.city = weChatUser.city || '未知';
                    userInfo.country = weChatUser.country || '未知';
                    userInfo.profile_image = weChatUser.headimgurl;
                    userInfo.gender = weChatUser.sex || 1;
                    userInfo.name = weChatUser.nickname || '匿名用户(微信)';
                    userInfo.privilege = weChatUser.privilege || '';
                    userInfo.union_id = weChatUser.unionid;
                    userInfo.wall_id = wallId;
                    userInfo.is_anonymous = !weChatUser.nickname;
                    me.saveOrUpdateWallUser(userInfo, (id) => {
                        let wall = {
                            id: wallId,
                            administrators: [],
                            description: '跨屏朋友圈是一款内容互动应用，可以将品牌的内容与用户通过不同的方式互动起来，有趣而生动。'
                        };
                        wall.administrators.push(id);
                        me.saveOrUpdateWall(wall, (result) => { });
                        userInfo.id = id;
                        callback(null, userInfo, wallId);
                    });
                } else {
                    callback(null, users[0], wallId);
                }
            },
            (wallUser, wallId, callback) => {
                me.crawlerService.fetchWeChatArticle(url, (article) => {
                    callback(null, wallUser, wallId, article);
                });
            },
            (wallUser, wallId, article, callback) => {
                if (article) {
                    article.title = article.title || (data && data.Title);
                    article.description = article.description || (data && data.Description);
                    article.url = article.url || (data && data.Url);

                    me.savePhotoAssset(wallId, {
                        photo_url: article.picurl,
                        description: article.title,
                        wall_id: wallId,
                        asset_meta_data: {},
                        additional_url: article.url,
                        asset_type: 'photo',
                        source: 'wechat-link-message'
                    }, wallUser, (result) => {
                        var articles = [];
                        if (result.success) {
                            article.url = `http://${me.applicationConfig.host}/mkprobe/${btoa(article.url).replace(/\//g, '-')}/${wallId}/${result.data.id}`;
                            articles.push(article);
                        }

                        if (article.picurl && article.picurl.startsWith('//')) {
                            article.picurl = `http:${article.picurl}`;
                        }
                        let wallLogo = me.getCDNImageURL(wallInfo.logo);
                        if (wallLogo && wallLogo.startsWith('//')) {
                            wallLogo = `http:${wallLogo}`;
                        }
                        articles.push({
                            title: wallInfo.name,
                            picurl: wallLogo,
                            url: `http://${me.applicationConfig.host}/wall/${wallId}`
                        });

                        parentCallback(articles);
                    });
                } else {
                    parentCallback([]);
                }
            }
        ]);
    }
}

exports.WallService = WallService;

function formatData(me, params, result, cb) {
    var functionChain = [],
        retValue = [],
        count = 0;
    result.forEach((photo, j) => {
        var likes = photo.likes ? photo.likes.length : 0;
        // var isLiked = (photo.likes.indexOf(params.userId) >= 0);
        var photoId = (photo.id || photo._id).toString();
        var additionalUrl = (photo.additional_url ? (!photo.additional_url.startsWith("http://") ? "http://" + photo.additional_url : photo.additional_url) : "");
        var midVal = {
            id: photoId,
            user: {
                id: photo.creator_id
            },
            photo_url: me.getCDNImageURL(photo.photo_url),
            description: photo.description,
            ratio: photo.ratio,
            is_liked: true,
            likes: likes,
            status: photo.status,
            timestamp: photo.last_updated_date.getTime(),
            source: photo.source,
            additional_url: additionalUrl,
            asset_meta_data: photo.asset_meta_data || {},
            type: photo.asset_type
        };
        if (params.platform === 'console' && midVal.additional_url) {
            midVal.statistics = {};
            let clicks = photo.statistics && photo.statistics.clicks && photo.statistics.clicks.length || 0;
            let likes = photo.statistics && photo.statistics.likes && photo.statistics.likes.length || 0;
            let conversions = photo.statistics && photo.statistics.conversions && photo.statistics.conversions.length || 0;

            midVal.statistics.clicks = clicks;
            midVal.statistics.likes = likes;
            midVal.statistics.conversions = conversions;
        }
        if (midVal.additional_url) { //Added track for third party url
            let encodedURL = btoa(midVal.additional_url).replace(/\//g, '-');
            midVal.additional_url = `/mkprobe/${encodedURL}/${wallId}/${midVal.id}`
        }

        if (midVal.source == 'PC') {
            functionChain.push((callback) => {
                me.User.findUserById(midVal.user.id, (error, user) => {
                    if (user) {
                        midVal.user = {
                            id: user.id,
                            name: user.name,
                            profile_image: me.getCDNImageURL(user.profile_image)
                        };
                    } else {
                        midVal.user = {
                            id: null,
                            name: "匿名用户",
                            profile_image: me.getCDNImageURL('dashboard/images/default_profile_image.png')
                        };
                    }
                    retValue.push(midVal);
                    callback(null);
                });
            });
        } else {
            functionChain.push((callback) => {
                me.WallUser.findWallUserById(midVal.user.id, (error, user) => {
                    if (user) {
                        midVal.user.id = user.id;
                        midVal.user.profile_image = me.getCDNImageURL(user.profile_image);
                        midVal.user.name = user.name;
                    } else {
                        midVal.user = {
                            id: null,
                            name: "匿名用户",
                            profile_image: me.getCDNImageURL('dashboard/images/default_profile_image.png')
                        };
                    }
                    retValue.push(midVal);
                    callback(null);
                });
            });
        }
        functionChain.push((callback) => {
            me.Comment.findCommentCountByPhotoId(midVal.id, (error, result) => {
                if (error) {
                    return '';
                } else {
                    count++;
                    midVal.comments_count = result;
                    callback(null);
                }
            });
        });
    });
    functionChain.push(function () {
        cb(null, retValue);
    });
    async.waterfall(functionChain);
}

function changeStatus(me, userId, action, photo, parentCallback) {
    switch (action) {
        case 'like':
            if (photo.likes.indexOf(userId) >= 0) {
                parentCallback(null, photo.likes.length);
            } else {
                photo.likes.push(userId);
                photo.save(function (error, doc) {
                    if (error) {
                        parentCallback(photo.likes.length);
                    } else {
                        parentCallback(doc.likes.length);
                    }
                });
            }
            break;
        case 'unlike':
            if (photo.likes.indexOf(userId) >= 0) {
                var index = photo.likes.indexOf(userId);
                photo.likes.splice(index, 1);
                photo.save(function (error, doc) {
                    if (error) {
                        parentCallback(photo.likes.length);
                    } else {
                        parentCallback(doc.likes.length);
                    }
                });
            } else {
                parentCallback(photo.likes.length);
            }
            break;
        case 'approve':
        case 'reject':
        case 'delete':
            photo.last_updated_date = new Date();
            if (photo.status !== me.wallPhotoOperationMap[action]) {
                photo.status = me.wallPhotoOperationMap[action];
                photo.operations.push({
                    operator_id: userId,
                    operation: action,
                    timestamp: Date.now()
                });
                photo.save(function (error, photo) {
                    if (error) {
                        parentCallback(null);
                    } else {
                        parentCallback(true);
                    }
                });
            } else {
                parentCallback(true);
            }
            break;
    }
}